package org.yutian.springboot.freemarker;

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;

/**
 * @author zhoulei7
 * @create 2021-02-23
 **/
public class FreemarkerTool {
    // 不需要重复创建 Configuration 实例； 它的代价很高，尤其是会丢失缓存。Configuration 实例就是应用级别的单例。
    // Configuration 中不处理修改设置的方法是线程安全的。
    private static final Configuration cfg = prepareConfiguration();

    private static Configuration prepareConfiguration() {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
        try {
            // 模板目录
            // cfg.setDirectoryForTemplateLoading(new File("/where/you/store/templates"));
            FileTemplateLoader ftl1 = new FileTemplateLoader(new File("D:\\Users\\hasee\\work\\xjava\\springboot\\quickstartdemo\\templates\\ftl"));
            ClassTemplateLoader ctl = new ClassTemplateLoader(FreemarkerTool.class.getClass(), "/templates");
            TemplateLoader[] loaders = new TemplateLoader[] { ftl1, ctl };
            MultiTemplateLoader mtl = new MultiTemplateLoader(loaders);
            cfg.setTemplateLoader(mtl);
            cfg.setDefaultEncoding("UTF-8"); // 模板文件的编码
            // RETHROW_HANDLER 简单重新抛出所有异常而不会做其它的事情。 这个控制器对Web应用程序(假设你在发生异常之后不想继续执行模板)来说非常好， 因为它在生成的页面发生错误的情况下，给了你很多对Web应用程序的控制权 (因为FreeMarker不向输出中打印任何关于该错误的信息)。
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            cfg.setLogTemplateExceptions(false);
            // 设置共享变量
            setConfigure(cfg);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return cfg;
    }

    private static void setConfigure(Configuration configuration) throws Exception {
        // 为了支持静态文件cdn保存，故避免使用configuration.setSharedVariable("contextPath", contextPath);
        configuration.setSharedVariable("staticHostPath", "http://cdn");

        configuration.setSharedVariable("serverHostPath", "http://localhost");

        configuration.setSharedVariable("siteName", "雨田应用商店");
        // 不要在标题中加上搜索次数高、但与本页面无关的关键词。这方法10年前还管用，现在早已没有任何效果，还会被认为是作弊！ 有的时候品牌名称（雨田应用商店）比任何关键词都重要，那么标题最好就是品牌名称。
        configuration.setSharedVariable("title", "手机游戏应用商店_软件商店app下载-雨田应用商店");
        configuration.setSharedVariable("keywords", "手机应用商店,游戏应用商店,app商店下载,手机软件商店,软件商店app,雨田应用商店,雨田市场");
        configuration.setSharedVariable("description", "雨田应用商店是由雨田推出的手机软件商店合集，致力于成为全面、优质的手机游戏应用商店，在这里用户可以找到最新最好玩的手机app和游戏应用，并提供软件商店app下载，最新最好玩的应用和游戏尽在雨田应用商店");
        configuration.setSharedVariable("necessarySubjectId", "115150");
    }


    /**
     * 渲染：数据模型+模板=输出
     * @param prepareData
     * @param ftlName
     * @return
     * @throws IOException
     */
    public static String render(Map<String, Object> prepareData, String ftlName) {
        /**
         * 将会创建一个 ftlName 的 Template 实例，通过读取 ${cfg.setDirectoryForTemplateLoading}/ftlName 文件，之后解析(编译)它。Template 实例以解析后的形式存储模板， 而不是以源文件的文本形式。
         *
         * Configuration 缓存 Template 实例，当再次获得 ftlName 的时候，它可不再读取和解析模板文件了， 而只是返回第一次的 Template 实例。
         *
         * 如果更改了模板文件，当下次调用模板时，FreeMarker 将会自动重新载入和解析模板。FreeMarker再次检查模板所要间隔的时间。 其默认值是5秒。
         * 如果加载的同时，模板文件发生更新，则可能会有问题
         */
        try {
            Template temp = cfg.getTemplate(ftlName);
            StringWriter out = new StringWriter();
            // Writer out = new OutputStreamWriter();
            temp.process(prepareData, out);
            return  out.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
